</> 技術筆記Tech Notes

在 Raspberry Pi 5 (Ubuntu 25.04) 上使用 SQL 進行 PostgreSQL 效能基準測試之技術指南

1. 文件目的

本文件旨在提供一套標準作業程序,用以評估與分析在 Raspberry Pi 5 硬體平台上運行的 PostgreSQL 資料庫之效能。效能測試是資料庫管理與應用程式優化的核心環節。本指南將不依賴外部效能測試工具,而是聚焦於如何利用 SQL 指令,特別是 EXPLAIN ANALYZE,以及 PostgreSQL 內建的基準測試工具 pgbench,來獲取量化的效能指標。

本指南涵蓋的範圍包括:測試資料的準備、查詢計畫的分析、索引對效能影響的量化比較,以及讀寫負載的基礎測試。

2. 核心概念與工具

在開始測試前,必須理解以下核心概念:

  • 查詢計畫 (Query Plan): 當您執行一個 SQL 查詢時,PostgreSQL 的查詢規劃器 (Query Planner) 會分析多種執行該查詢的方式,並選擇一個它認為成本(Cost)最低的方案。這個方案即為「查詢計畫」。

  • EXPLAIN ANALYZE: 這是 PostgreSQL 中最強大的單一效能分析工具。

    • EXPLAIN: 顯示查詢規劃器預計會使用的查詢計畫,而不實際執行它。

    • ANALYZE: 實際執行查詢,並記錄下每個步驟的真實耗時與資源使用情況。

    • 兩者結合 EXPLAIN ANALYZE,可以讓我們比對預期與實際的效能,找出瓶頸所在。

  • 循序掃描 (Sequential Scan): 從頭到尾讀取整張資料表來尋找符合條件的資料列。對於大型資料表而言,這通常是效能低落的根源。

  • 索引掃描 (Index Scan): 透過索引(類似書本的目錄)來快速定位資料所在位置,避免讀取整張表,大幅提升查詢速度。

3. 程序一:準備基準測試資料

在空無一物的資料表上進行測試是沒有意義的。我們必須先建立一個包含足夠多資料的測試環境,才能模擬真實世界的負載。

  1. 登入資料庫: 使用 psql 登入您先前建立的資料庫(例如 my_project_db)。

    # 以 postgres 管理員身份登入
    sudo -u postgres psql -d my_project_db
    
  2. 建立測試資料表: 我們將建立一個 products 資料表,包含多種資料類型。

    CREATE TABLE products (
        id          SERIAL PRIMARY KEY,
        product_sku UUID DEFAULT gen_random_uuid(),
        name        VARCHAR(255) NOT NULL,
        category    VARCHAR(50),
        price       NUMERIC(10, 2),
        stock_count INTEGER,
        added_at    TIMESTAMPTZ DEFAULT now()
    );
    
  3. 插入大量測試資料: 使用 generate_series() 函數,我們可以快速地產生大量(此處為 100 萬筆)的隨機資料。

    INSERT INTO products (name, category, price, stock_count)
    SELECT
        'Product ' || s.id,
        CASE (s.id % 5)
            WHEN 0 THEN 'Electronics'
            WHEN 1 THEN 'Books'
            WHEN 2 THEN 'Clothing'
            WHEN 3 THEN 'Home Goods'
            ELSE 'Toys'
        END,
        (random() * 500 + 10)::NUMERIC(10, 2),
        (random() * 1000)::INTEGER
    FROM generate_series(1, 1000000) AS s(id);
    

    注意: 在 Raspberry Pi 5 上,此操作可能需要數分鐘時間,具體取決於 microSD 卡的寫入速度。

4. 程序二:執行 SQL 查詢效能測試

4.1. 基準測試:循序掃描 (Sequential Scan)

首先,我們在沒有任何索引的情況下,查詢一個特定的產品。

EXPLAIN ANALYZE SELECT * FROM products WHERE name = 'Product 500000';

預期輸出分析: 您會看到查詢計畫的頂層節點顯示為 Seq Scan on products。請特別記下最下方的 Execution Time,這將是我們的效能基準。在百萬筆資料中,這個時間可能長達數百毫秒。

                                                     QUERY PLAN
---------------------------------------------------------------------------------------------------------------------
 Seq Scan on products  (cost=0.00..20526.00 rows=1 width=64) (actual time=0.024..158.361 rows=1 loops=1)
   Filter: ((name)::text = 'Product 500000'::text)
   Rows Removed by Filter: 999999
 Planning Time: 0.106 ms
 Execution Time: 158.391 ms  <-- 記下此數值

4.2. 優化測試:索引掃描 (Index Scan)

現在,我們在 name 欄位上建立一個 B-Tree 索引。

CREATE INDEX idx_products_name ON products(name);

建立索引後,執行完全相同的查詢:

EXPLAIN ANALYZE SELECT * FROM products WHERE name = 'Product 500000';

預期輸出分析: 這次,查詢計畫應顯示為 Index Scan using idx_products_name on products。您會發現 Execution Time 大幅縮短,可能降至 1 毫秒以下。這清晰地展示了索引帶來的巨大效能提升。

QUERY PLAN
Index Scan using idx_products_name on products  (cost=0.42..8.44 rows=1 width=60) (actual time=0.034..0.035 rows=1 loops=1)
Index Cond: ((name)::text = 'Product 500000'::text)
Planning Time: 0.338 ms
Execution Time: 0.053 ms <-- 與前一個數值進行比較

4.3. 聚合查詢測試

聚合查詢(如 GROUP BY)主要消耗 CPU 資源。此測試可評估伺服器在資料處理上的效能。

EXPLAIN ANALYZE SELECT category, AVG(price) FROM products GROUP BY category;

觀察其 Execution Time,並注意查詢計畫中可能出現的 HashAggregate 節點。

QUERY PLAN
Finalize GroupAggregate  (cost=18985.15..18986.45 rows=5 width=40) (actual time=206.724..213.276 rows=5 loops=1)
Group Key: category
->  Gather Merge  (cost=18985.15..18986.31 rows=10 width=40) (actual time=206.699..213.239 rows=15 loops=1)
Workers Planned: 2
Workers Launched: 2
->  Sort  (cost=17985.12..17985.13 rows=5 width=40) (actual time=202.269..202.271 rows=5 loops=3)
Sort Key: category
Sort Method: quicksort  Memory: 25kB
Worker 0:  Sort Method: quicksort  Memory: 25kB
Worker 1:  Sort Method: quicksort  Memory: 25kB
->  Partial HashAggregate  (cost=17985.00..17985.06 rows=5 width=40) (actual time=202.218..202.221 rows=5 loops=3)
Group Key: category
Batches: 1  Memory Usage: 24kB
Worker 0:  Batches: 1  Memory Usage: 24kB
Worker 1:  Batches: 1  Memory Usage: 24kB
->  Parallel Seq Scan on products  (cost=0.00..15901.67 rows=416667 width=14) (actual time=0.014..45.071 rows=333333 loops=3)
Planning Time: 0.206 ms
Execution Time: 213.339 ms

5. 程序三:讀寫 (OLTP) 負載基準測試

單一的 SQL 查詢無法完全代表真實世界的應用負載。pgbench 是 PostgreSQL 內建的標準工具,用於模擬多個客戶端同時對資料庫進行讀寫操作(交易)。

注意: 以下指令需在 psql 之外的 Linux Shell 環境中執行。

  1. 初始化 pgbench 環境: 此指令會建立 pgbench 所需的四張資料表。-s 10(scale factor 10)將建立一個中等大小的測試資料集(10 * 100,000 = 100 萬筆 pgbench_accounts 資料列)。

    pgbench -i -s 10 my_project_db
    
  2. 執行基準測試: 此指令模擬 10 個客戶端 (-c 10),使用 2 個執行緒 (-j 2),執行 1000 筆交易 (-t 1000)。

    pgbench -c 10 -j 2 -t 1000 my_project_db
    
  3. 分析輸出結果: 測試結束後,您會看到一份報告。最重要的指標是 tps (transactions per second)。

    pgbench (17.5 (Ubuntu 17.5-0ubuntu0.25.04.1))
    starting vacuum...end.
    transaction type: <builtin: TPC-B (sort of)>
    scaling factor: 10
    query mode: simple
    number of clients: 10
    number of threads: 2
    maximum number of tries: 1
    number of transactions per client: 1000
    number of transactions actually processed: 10000/10000
    number of failed transactions: 0 (0.000%)
    latency average = 5.689 ms
    initial connection time = 130.366 ms
    tps = 1757.718538 (without initial connection time)
    

    此處的 tps 值(約 1757)可作為您 Raspberry Pi 5 在此特定負載下的 OLTP 效能基準。

6. 結論

本指南提供了一套基礎的 PostgreSQL 效能測試方法。透過 EXPLAIN ANALYZE,我們可以精確地診斷單一查詢的效能瓶頸,並驗證索引等優化手段的有效性。透過 pgbench,我們可以獲得一個關於伺服器處理並發讀寫交易能力的量化指標 (TPS)。

這些測試結果是後續進行系統調校(如修改 postgresql.conf 中的 shared_bufferswork_mem 等參數)或硬體升級(如使用更高速的 NVMe SSD 替代 microSD 卡)前後,評估效能變化的重要依據。